package com.dingding.web.filter;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

import com.dingding.common.core.util.JSONUtil;
import com.dingding.facde.coupon.dto.ResponseDataDTO;

/**
 * 
 * @ClassName: IpInterceptor 
 * @Description:自定义过滤器，用来判断IP访问次数是否超限。
 *              如果前台用户访问网站的频率过快（比如：达到或超过50次/s），则判定该IP为恶意刷新操作，限制该ip访问<br> 
 *              默认限制访问时间为1小时，一小时后自定解除限制 
 * @author CHEHAUNBO 
 * @date 2016年9月22日 下午3:46:39
 * 
 */
public class IpInterceptor implements Filter {

	private static final Logger log = Logger.getLogger(IpInterceptor.class);

	/** 
	 * 默认限制时间（单位：ms） 
	 */
	private static final long LIMITED_TIME_MILLIS = (60 * 60 * 1000) * 24;

	/** 
	 * 用户连续访问最高阀值，超过该值则认定为恶意操作的IP，进行限制 
	 */
	private static final int LIMIT_NUMBER = 15;

	/** 
	 * 用户访问最小安全时间，在该时间内如果访问次数大于阀值，则记录为恶意IP，否则视为正常访问 
	 */
	private static final int MIN_SAFE_TIME = 5000;
	
	/**
	 * 以资源文件结尾的网络请求
	 */
	private final static Pattern FILTERS = Pattern.compile(".*(\\.(css|js|gif|jpg|png|mp3|mp3|zip|gz))$");
	
	/**
	 * 错误页面
	 */
	private final static String ERROR_HTML = "/dingding-web/common/errorinfo.html";

	private FilterConfig config;

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		this.config = filterConfig;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;
		
		String requestUri = request.getRequestURI();
		
		//如果是资源文件 或者 访问的是错误页面 ，则不被拦截器拦截
		if(FILTERS.matcher(requestUri).matches()){
			chain.doFilter(request, response);
			return;
		}else if(requestUri.equals(ERROR_HTML)){
			chain.doFilter(request, response);
			return;
		}

		ServletContext context = config.getServletContext();

		// 获取限制IP存储器：存储被限制的IP信息
		Map<String, Long> limitedIpMap = (Map<String, Long>) context.getAttribute("limitedIpMap");
		// 获取IP存储器
		Map<String, Long[]> ipMap = (Map<String, Long[]>) context.getAttribute("ipMap");

		if (limitedIpMap == null) {

			ipMap = new ConcurrentHashMap<String, Long[]>();
			limitedIpMap = new ConcurrentHashMap<String, Long>();
			context.setAttribute("ipMap", ipMap);
			context.setAttribute("limitedIpMap", limitedIpMap);
		}

		// 过滤受限的IP
		filterLimitedIpMap(limitedIpMap);
		// 获取用户IP
		String ip = request.getRemoteHost();

		// 判断是否是被限制的IP，如果是则跳到异常页面
		if (isLimitedIP(limitedIpMap, ip)) {

			log.error("发现刷单IP:" + ip);
//			redirect(response,"/dingding-web/common/errorinfo.html");
			request.getRequestDispatcher("/common/errorinfo.html").forward(request, response);

			return;
		}

		// 判断存储器中是否存在当前IP，如果没有则为初次访问，初始化该ip
		// 如果存在当前ip，则验证当前ip的访问次数
		// 如果大于限制阀值，判断达到阀值的时间，如果不大于[用户访问最小安全时间]则视为恶意访问，跳转到异常页面
		if (ipMap.containsKey(ip)) {
			Long[] ipInfo = ipMap.get(ip);
			// 访问次数+1
			ipInfo[0] = ipInfo[0] + 1;
			// 单位时间内超过访问阈值
			if (ipInfo[0] > LIMIT_NUMBER) {
				// 获取当前IP的初次访问时间
				Long ipAccessTime = ipInfo[1];
				Long currentTimeMillis = System.currentTimeMillis();
				if (currentTimeMillis - ipAccessTime <= MIN_SAFE_TIME) {
					// 设置取消限制时间
					limitedIpMap.put(ip, currentTimeMillis + LIMITED_TIME_MILLIS);
					log.error("发现刷单IP:" + ip);
					request.getRequestDispatcher("/common/errorinfo.html").forward(request, response);
//					redirect(response,"/dingding-web/common/errorinfo.html");
					return;
				} else {
					initIpVisitsNumber(ipMap, ip);
				}
			}
		} else {
			initIpVisitsNumber(ipMap, ip);
		}

		context.setAttribute("ipMap", ipMap);
		chain.doFilter(request, response);

	}

	private void responseData(HttpServletResponse response, String text) throws IOException {

		ResponseDataDTO responseDataDTO = new ResponseDataDTO();
		responseDataDTO.setCode(403);
		responseDataDTO.setErrorMessage(text);
		response.setCharacterEncoding("UTF-8");
		response.getWriter().write(JSONUtil.toJSONString(responseDataDTO));

	}

	@Override
	public void destroy() {

	}

	/**
	 * 
	 * 验证当前IP是否在黑名单中 
	 * 
	 * 
	 * @Title: isLimitedIP 
	 * @param limitedIpMap
	 * @param ip 访问IP
	 * @return  boolean true 存在 false  不存在
	 * @author CHEHAUNBO
	 * 
	 * 
	 */
	private boolean isLimitedIP(Map<String, Long> limitedIpMap, String ip) {

		if (limitedIpMap == null || ip == null) {
			// 没有被限制
			return false;
		}
		Set<String> keys = limitedIpMap.keySet();
		Iterator<String> keyIt = keys.iterator();
		while (keyIt.hasNext()) {
			String key = keyIt.next();
			if (key.equals(ip)) {
				return true;
			}
		}
		return false;

	}

	/**
	 * 
	 * 过滤受限的IP，剔除已经到期的限制IP 
	 * 
	 * @Title: filterLimitedIpMap 
	 * @param limitedIpMap 黑名单集合
	 * @author CHEHAUNBO
	 * 
	 */
	private void filterLimitedIpMap(Map<String, Long> limitedIpMap) {

		if (limitedIpMap == null) {
			return;
		}

		Set<String> keys = limitedIpMap.keySet();
		Iterator<String> keyIt = keys.iterator();
		long currentTimeMillis = System.currentTimeMillis();
		while (keyIt.hasNext()) {
			long expireTimeMillis = limitedIpMap.get(keyIt.next());
			if (expireTimeMillis <= currentTimeMillis) {
				keyIt.remove();
			}
		}

	}

	/**
	 * 初始化用户访问次数和访问时间 
	 * 
	 * @Title: initIpVisitsNumber 
	 * @param ipMap 
	 * @param ip  访问的IP
	 * @author CHEHAUNBO
	 * 
	 */
	private void initIpVisitsNumber(Map<String, Long[]> ipMap, String ip) {
		Long[] ipInfo = new Long[2];
		ipInfo[0] = 0L;// 访问次数
		ipInfo[1] = System.currentTimeMillis();// 初次访问时间
		ipMap.put(ip, ipInfo);
	}
	
	private void redirect(HttpServletResponse response,String url){
		try {
			response.sendRedirect(url);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}